using System;
using System.IO;
using System.Windows.Forms;
using TFlex.Model;
using TFlex.Model.Model3D;
using TFlex.Model.Model3D.Geometry;

namespace External3D
{
	// Class for cube objects
	//
	// Класс куба
	public class Cube : CentralSymmetry
	{
		// Tolerance for various calculations
		//
		// Точность вычислений
		public const double Epsilon = 1.0e-6;

		protected new const int FormatVersion = 0;

		// Length of cube edges
		//
		// Длина граней куба
		private double m_edgeLength = 0.0;
		public double EdgeLength
		{
			get { return m_edgeLength; }
			set { m_edgeLength = value; }
		}

		// Direction of one of cube edges
		//
		// Направление одной стороны куба
		private Direction m_firstDirection = new Direction(1.0, 0.0, 0.0);
		public Direction FirstDirection
		{
			get { return m_firstDirection; }
			set { m_firstDirection = value; }
		}

		// Direction of another cube edge
		//
		// Направление другой стороны куба
		private Direction m_secondDirection = new Direction(0.0, 1.0, 0.0);
		public Direction SecondDirection
		{
			get { return m_secondDirection; }
			set { m_secondDirection = value; }
		}

		// Identifier of this object type
		//
		// Идентификатор класса куба
		protected override int TypeID { get { return (int)ObjectTypeIDs.CubeID; } }

		// Prefix for automatically-generated object names
		//
		// Префикс для задания имени куба
		protected override string TypeName { get { return External3DPlugin.Resources.GetString("prefixes.cube"); } }

		// Identifier of the icon used to display objects of this type in various user interface elements (model tree,
		// etc.)
		//
		// Идентификатор иконки для обозначения куба
		protected override int IconID { get { return (int)ObjectTypeIcons.CubeIcon; } }

		// Current version of this object type
		//
		// Текущая версия объекта
		protected override int Version { get { return CentralSymmetry.FormatVersion + FormatVersion; } }

		// Constructs a cube
		//
		// Конструирует куб
		public Cube()
			: base()
		{
		}

		// Constructs a cube. This constructor is used when the object is being created by the
		// External3D.External3DPlugin.CreateObject(TFlex.Model.Document, IntPtr, int) method
		//
		// Конструирует куб. Вызывается при создании объекта с помощью метода
		// External3D.External3DPlugin.CreateObject(TFlex.Model.Document, IntPtr, int)
		public Cube(IntPtr ownerHandle)
			: base(ownerHandle)
		{
		}

		// Creates and returns a copy of this object. This method is called by the
		// TFlex.Model.Model3D.ExternalOperation.ExternalOperation(TFlex.Model.Model3D.ProxyOperation,
		//                                                         TFlex.Model.Document,
		//                                                         TFlex.Plugin)
		// constructor
		//
		// Возвращает копию объекта. Вызывается из конструктора
		// TFlex.Model.Model3D.ExternalOperation.ExternalOperation(TFlex.Model.Model3D.ProxyOperation,
		//                                                         TFlex.Model.Document,
		//                                                         TFlex.Plugin)
		protected override ProxyObject Clone(IntPtr ownerHandle)
		{
			Cube clone = (Cube)base.Clone(ownerHandle);
			clone.EdgeLength = m_edgeLength;
			clone.FirstDirection = new Direction(m_firstDirection);
			clone.SecondDirection = new Direction(m_secondDirection);

			return clone;
		}

		// Determines whether this object is equal to 'obj' or not
		//
		// Определяет, равен этот объект объекту 'obj' или нет
		protected override bool Equals(TFlex.Model.ProxyObject obj)
		{
			if (!base.Equals(obj))
				return false;

			Cube other = obj as Cube;
			return other != null
			       && Math.Abs(m_edgeLength - other.m_edgeLength) < Epsilon
			       && m_firstDirection == other.m_firstDirection
			       && m_secondDirection == other.m_secondDirection;
		}

		// Reads parameters of this object from the specified stream
		//
		// Чтение данных
		protected override void Read(Stream stream, int version)
		{
			int baseVersion = version - FormatVersion;
			base.Read(stream, baseVersion);

			int myVersion = version - CentralSymmetry.FormatVersion;
			BinaryReader reader = new BinaryReader(stream);
			m_edgeLength = reader.ReadDouble();
			m_firstDirection.X = reader.ReadDouble();
			m_firstDirection.Y = reader.ReadDouble();
			m_firstDirection.Z = reader.ReadDouble();
			m_secondDirection.X = reader.ReadDouble();
			m_secondDirection.Y = reader.ReadDouble();
			m_secondDirection.Z = reader.ReadDouble();
		}

		// Writes parameters of this object to the specified stream
		//
		// Запись данных
		protected override void Write(Stream stream)
		{
			base.Write(stream);

			BinaryWriter writer = new BinaryWriter(stream);
			writer.Write(m_edgeLength);
			writer.Write(m_firstDirection.X);
			writer.Write(m_firstDirection.Y);
			writer.Write(m_firstDirection.Z);
			writer.Write(m_secondDirection.X);
			writer.Write(m_secondDirection.Y);
			writer.Write(m_secondDirection.Z);
		}

		// Creates geometry of this object
		//
		// Генерация геометрии объекта
		protected override bool MakeGeometry()
		{
			if (!UpdateParameters())
				return false;

			// Object parameters are stored in model units but calculations expect them to be specified in meters
			//
			// Параметры кубика хранятся в единицах модели. Для расчетов их нужно преобразовывать в метры
			UnitConverter converter = new UnitConverter(Operation.Document);

			Direction normal = FirstDirection.CrossProduct(SecondDirection).NormalizedVector();

			Body profile = MakeProfile(converter, normal);
			if (profile == null)
				return false;

			// Create a cube by extruding one of its faces
			//
			// Построить кубик выталкиванием грани кубика по нормали
			ExtrudeGenerator extrusion = new ExtrudeGenerator(this, normal, profile, converter.ToMeter(m_edgeLength), 0.0);
			if (!extrusion.Run())
				return false;

			// Add the cube to the list of bodies which form this object
			//
			// Добавить кубик в множество результирующих тел внешней операции
			foreach (Body body in extrusion.Result)
				AddSolid(body);

			return true;
		}

		// Fills 'menu' with context menu commands available for this object
		//
		// Контекстное меню объекта
		protected override bool GetContextMenu(TFlex.Menu menu)
		{
			menu.Append((int)ObjectCommands.EditCube,
			            External3DPlugin.Resources.GetString("commands.edit_cube"),
			            true,
			            false,
			            Plugin);
			return true;
		}

		// Handles context menu commands
		//
		// Обработка команд контекстного меню
		protected override bool OnCommand(int id, TFlex.Model.View view)
		{
			switch (id)
			{
			case (int)ObjectCommands.EditCube:
				{
					CubeCreateCommand command = new CubeCreateCommand(Plugin);
					command.EditCubeOperation = Operation;
					command.Run(Operation.Document.ActiveView);
					return true;
				}

			default:
				return false;
			}
		}

		// Fills 'properties' with measurable properties of this object
		//
		// Получить список измеримых свойств объекта
		protected override void GetPropList(TFlex.Model.PropertyArray properties)
		{
			properties.Add("CEdge",
			               External3DPlugin.Resources.GetString("properties.cube.edge_length"),
			               TFlex.Model.PropertyArray.Type.Real);
			properties.Add("CXCentre",
			               External3DPlugin.Resources.GetString("properties.cube.center.x"),
			               TFlex.Model.PropertyArray.Type.Real);
			properties.Add("CYCentre",
			               External3DPlugin.Resources.GetString("properties.cube.center.y"),
			               TFlex.Model.PropertyArray.Type.Real);
			properties.Add("CZCentre",
			               External3DPlugin.Resources.GetString("properties.cube.center.z"),
			               TFlex.Model.PropertyArray.Type.Real);
			properties.Add("CXEdge1",
			               External3DPlugin.Resources.GetString("properties.cube.first_edge.x"),
			               TFlex.Model.PropertyArray.Type.Real);
			properties.Add("CYEdge1",
			               External3DPlugin.Resources.GetString("properties.cube.first_edge.y"),
			               TFlex.Model.PropertyArray.Type.Real);
			properties.Add("CZEdge1",
			               External3DPlugin.Resources.GetString("properties.cube.first_edge.z"),
			               TFlex.Model.PropertyArray.Type.Real);
			properties.Add("CXEdge2",
			               External3DPlugin.Resources.GetString("properties.cube.second_edge.x"),
			               TFlex.Model.PropertyArray.Type.Real);
			properties.Add("CYEdge2",
			               External3DPlugin.Resources.GetString("properties.cube.second_edge.y"),
			               TFlex.Model.PropertyArray.Type.Real);
			properties.Add("CZEdge2",
			               External3DPlugin.Resources.GetString("properties.cube.second_edge.z"),
			               TFlex.Model.PropertyArray.Type.Real);
		}

		// Returns value of the specified numeric property of this object
		//
		// Измерить свойство объекта
		protected override double GetRealProp(string name)
		{
			switch (name)
			{
			case "CEdge":
				return m_edgeLength;

			case "CXCentre":
				return m_centre.X;

			case "CYCentre":
				return m_centre.Y;

			case "CZCentre":
				return m_centre.Z;

			case "CXEdge1":
				return m_firstDirection.X;

			case "CYEdge1":
				return m_firstDirection.Y;

			case "CZEdge1":
				return m_firstDirection.Z;

			case "CXEdge2":
				return m_secondDirection.X;

			case "CYEdge2":
				return m_secondDirection.Y;

			case "CZEdge2":
				return m_secondDirection.Z;

			default:
				return 0.0;
			}
		}

		// Changes properties of this object
		//
		// Редактировать свойства объекта
		protected override PropertyChange EditProperties()
		{
			CubeDialog dialog = new CubeDialog();
			dialog.OperationName = Operation.Name;
			dialog.EdgeLength = EdgeLength;
			dialog.CentreCoords = Centre;
			dialog.EditCentre = Operation.GetReference((int)KeyReference.CentreID) == null;
			dialog.FirstDirection = FirstDirection;
			dialog.EditFirstDirection = Operation.GetReference((int)KeyReference.FirstDirectionID) == null;
			dialog.SecondDirection = SecondDirection;
			dialog.EditSecondDirection = Operation.GetReference((int)KeyReference.SecondDirectionID) == null;

			if (dialog.ShowDialog(null) != DialogResult.OK)
				return PropertyChange.Nothing;

			PropertyChange change = dialog.OperationName.Equals(Operation.Name)
			                      ? PropertyChange.Nothing
			                      : PropertyChange.WeakChange;

			if (Math.Abs(dialog.EdgeLength - EdgeLength) >= Epsilon
			    || dialog.CentreCoords != m_centre
			    || dialog.FirstDirection != m_firstDirection
			    || dialog.SecondDirection != m_secondDirection)
			{
				change = PropertyChange.Regenerate;
			}

			if (change != PropertyChange.Nothing)
			{
				Document document = Operation.Document;
				document.BeginChanges(External3DPlugin.Resources.GetString("commands.edit_cube"));

				Cube newCube = Operation.VolatileObject as Cube;
				newCube.Operation.Name = dialog.OperationName;
				newCube.EdgeLength = dialog.EdgeLength;

				if (dialog.EditCentre)
					newCube.Centre = dialog.CentreCoords;
				if (dialog.EditFirstDirection)
					newCube.FirstDirection = dialog.FirstDirection;
				if (dialog.EditSecondDirection)
					newCube.SecondDirection = dialog.SecondDirection;

				document.EndChanges();
			}

			return change;
		}

		// Retrieves parent objects and transforms this object accordingly
		//
		// Обновляет данные о положении куба с геометрии родительских элементов, если они заданы и пересчитаны
		private bool UpdateParameters()
		{
			if (m_edgeLength < Epsilon)
				return false;

			UnitConverter converter = new UnitConverter(Operation.Document);

			Geometry geom = Operation.GetGeomReference((int)KeyReference.CentreID);
			if (geom != null)
			{
				ModelPoint3D point = geom as ModelPoint3D;
				if (point != null)
					Centre = converter.FromMeter(point);
			}

			geom = Operation.GetGeomReference((int)KeyReference.FirstDirectionID);
			if (geom != null)
			{
				ModelDirection direction = geom as ModelDirection;
				if (direction != null)
					FirstDirection = converter.FromMeter(direction);
			}

			geom = Operation.GetGeomReference((int)KeyReference.SecondDirectionID);
			if (geom != null)
			{
				ModelDirection direction = geom as ModelDirection;
				if (direction != null)
					SecondDirection = converter.FromMeter(direction);
			}

			return m_firstDirection.Magnitude >= Epsilon
			       && m_secondDirection.Magnitude >= Epsilon
			       && Math.Abs(m_firstDirection * m_secondDirection) < Epsilon;
		}

		// Creates and returns one of cube faces
		//
		// Создать одну из граней кубика
		private Body MakeProfile(UnitConverter converter, Direction normal)
		{
			// Create a wire body forming contour of the face being created
			//
			// Составная кривая, в параметрической области плоскости грани
			Interval interval = new Interval(0.0, converter.ToMeter(m_edgeLength));
			WireFromCurvesGenerator.Segment[] segments = new WireFromCurvesGenerator.Segment[4];
			segments[0] = new WireFromCurvesGenerator.Segment(new LineCurve(new Axis(new Point3D(0.0, 0.0, 0.0),
			                                                                         new Direction(1.0, 0.0, 0.0))),
			                                                  interval,
			                                                  (int)TopolID.Edge1ID);
			segments[1] = new WireFromCurvesGenerator.Segment(new LineCurve(new Axis(converter.ToMeter(new Point3D(m_edgeLength, 0.0, 0.0)),
			                                                                         new Direction(0.0, 1.0, 0.0))),
			                                                  interval,
			                                                  (int)TopolID.Edge2ID);
			segments[2] = new WireFromCurvesGenerator.Segment(new LineCurve(new Axis(converter.ToMeter(new Point3D(m_edgeLength, m_edgeLength, 0.0)),
			                                                                         new Direction(-1.0, 0.0, 0.0))),
			                                                  interval,
			                                                  (int)TopolID.Edge3ID);
			segments[3] = new WireFromCurvesGenerator.Segment(new LineCurve(new Axis(converter.ToMeter(new Point3D(0.0, m_edgeLength, 0.0)),
			                                                                         new Direction(0.0, -1.0, 0.0))),
			                                                  interval,
			                                                  (int)TopolID.Edge4ID);
			WireFromCurvesGenerator wire = new WireFromCurvesGenerator(this, segments);
			if (!wire.Run() || wire.Result.Length != 1)
				return null;

			Body wireBody = wire.Result[0];
			wireBody.NormalizeIfPlanarClosedWire(new Direction(0.0, 0.0, 1.0));

			// Create a sheet body representing one of cube faces
			//
			// Листовое тело, в котором создаётся первая грань
			SheetFromTrimmedSurface.Lump.Loop[] loops = new SheetFromTrimmedSurface.Lump.Loop[1];
			loops[0] = new SheetFromTrimmedSurface.Lump.Loop(wireBody, (int)TopolID.LoopID);

			SheetFromTrimmedSurface.Lump[] lumps = new SheetFromTrimmedSurface.Lump[1];
			lumps[0] = new SheetFromTrimmedSurface.Lump(loops, (int)TopolID.FaceID);

			Point3D origin = converter.ToMeter(m_centre
			                                   + normal * (-m_edgeLength / 2.0)
			                                   + m_firstDirection.NormalizedVector() * (-m_edgeLength / 2.0)
			                                   + m_secondDirection.NormalizedVector() * (-m_edgeLength / 2.0));
			PlaneSurface plane = new PlaneSurface(new Plane(origin, normal, m_firstDirection.NormalizedVector()));
			SheetFromTrimmedSurface sheet = new SheetFromTrimmedSurface(this, plane, lumps);
			return sheet.Run() && sheet.Result.Length == 1
			       ? sheet.Result[0]
			       : null;
		}

		// Identifiers of keys used to store references to parent objects. These identifiers are written to document
		// files and therefore should never be changed in order to keep backward compatibility
		//
		// Идентификаторы ключей для хранения ссылок на родительские объекты
		public enum KeyReference
		{
			// Cube center
			//
			// Центр куба
			CentreID = 0,

			// Direction of one of the cube edges
			//
			// Направление одной стороны куба
			FirstDirectionID = 1,

			// Direction of another cube edge
			//
			// Направление другой стороны куба
			SecondDirectionID = 2
		};

		// Identifiers of various geometric elements used to build cubes
		//
		// Идентификаторы отдельных геометрических элементов, создаваемых в процессе построения тела
		private enum TopolID
		{
			// Edges of the face
			//
			// Рёбра грани - основания куба
			Edge1ID = 1,
			Edge2ID = 2,
			Edge3ID = 3,
			Edge4ID = 4,

			// Loop of the face
			//
			// Цикл грани - основания куба
			LoopID = 5,

			// Face
			//
			// Грань - основания куба
			FaceID = 6
		};
	}
}
